home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / src / X11 / xmbase-grok-1.2 / formop.c < prev    next >
C/C++ Source or Header  |  1995-06-25  |  18KB  |  615 lines

  1. /*
  2.  * this file is part of the form editor, it is mostly called by callbacks in
  3.  * formwin.c. It allocates forms and items, finds good defaults for things,
  4.  * and does some form geometry operations. Actual form drawing is done in
  5.  * preview.c (for the edit canvas) and cardwin.c (for final cards).
  6.  *
  7.  *    form_create()            allocate form with default parms
  8.  *    form_delete(form)        free form and all items in it
  9.  *    verify_form(form,bug,shell)    make sure that form is consistent
  10.  *    form_edit_script(form,sh,fn)    start up editor for form script
  11.  *    form_sort(form)            sort form's items by y/x position
  12.  *
  13.  *    item_deselect(form)        deselect all items in canvas
  14.  *    item_create(form, nitem)    allocate item and figure out defaults
  15.  *    item_delete(form, nitem)    free item and remove from form
  16.  */
  17.  
  18. #include "config.h"
  19. #include <X11/Xos.h>
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <errno.h>
  23. #include <Xm/Xm.h>
  24. #include "grok.h"
  25. #include "form.h"
  26. #include "proto.h"
  27.  
  28. #define CHUNK        10        /* alloc 10 new item ptrs at a time */
  29. #define XSNAP(x)    ((x)-(x)%form->xg)
  30. #define YSNAP(y)    ((y)-(y)%form->yg)
  31.  
  32. static void        set_form_defaults(FORM *);
  33. extern Widget        toplevel;    /* top-level shell for icon name */
  34.  
  35.  
  36. /*------------------------------------------------- operations on form ------*/
  37. /*
  38.  * allocate a new empty form struct, and fill it with reasonable default
  39.  * paramaters.
  40.  */
  41.  
  42. FORM *form_create(void)
  43. {
  44.     FORM        *form;        /* new form */
  45.  
  46.     if (!(form = (FORM *)malloc(sizeof(FORM)))) {
  47.         create_error_popup(toplevel, errno, "Can't create form");
  48.         return(0);
  49.     }
  50.     set_form_defaults(form);
  51.     return(form);
  52. }
  53.  
  54.  
  55. static void set_form_defaults(
  56.     FORM        *form)        /* for to initialize */
  57. {
  58.     mybzero((void *)form, sizeof(FORM));
  59.     form->cdelim     = ':';
  60.     form->xg     = 4;
  61.     form->yg     = 4;
  62.     form->xs     = XSNAP(400);
  63.     form->ys     = YSNAP(200);
  64.     form->autoquery  = -1;
  65. }
  66.  
  67.  
  68. /*
  69.  * clone a form. This is done when the form editor is started with a template
  70.  * form, we mustn't overwrite the form currently being displayed in the main
  71.  * menu.
  72.  */
  73.  
  74. FORM *form_clone(
  75.     FORM        *parent)    /* old form */
  76. {
  77.     FORM        *form;        /* new form */
  78.     int        i;
  79.  
  80.     if (!(form = (FORM *)malloc(sizeof(FORM)))) {
  81.         create_error_popup(toplevel, errno, "Can't clone form");
  82.         return(0);
  83.     }
  84.     *form = *parent;
  85.     form->path    = mystrdup(parent->path);
  86.     form->name    = mystrdup(parent->name);
  87.     form->dbase   = mystrdup(parent->dbase);
  88.     form->comment = mystrdup(parent->comment);
  89.     form->help    = mystrdup(parent->help);
  90.  
  91.     if (form->nitems &&
  92.         !(form->items = (ITEM **)malloc(form->size * sizeof(ITEM *)))) {
  93.         create_error_popup(toplevel, errno, "Can't clone field list");
  94.         form->nitems = 0;
  95.     }
  96.     for (i=0; i < form->nitems; i++)
  97.         form->items[i] = item_clone(parent->items[i]);
  98.  
  99.     if (form->query) {
  100.         if (!(form->query = malloc(form->nqueries * sizeof(DQUERY)))) {
  101.             create_error_popup(toplevel, errno, "Queries lost");
  102.             form->nqueries = 0;
  103.         }
  104.         for (i=0; i < form->nqueries; i++) {
  105.             form->query[i].suspended = parent->query[i].suspended;
  106.             form->query[i].name = mystrdup(parent->query[i].name);
  107.             form->query[i].query= mystrdup(parent->query[i].query);
  108.         }
  109.     }
  110.     return(form);
  111. }
  112.  
  113.  
  114. /*
  115.  * destroy a form struct and all its items. The pointer passed is not freed.
  116.  * Make sure to remove all windows that display a view of this form first.
  117.  * Initialize the form with defaults (this is used when reading form file).
  118.  */
  119.  
  120. void form_delete(
  121.     FORM        *form)        /* form to delete */
  122. {
  123.     DQUERY        *dq;        /* default query entry */
  124.     int        i;
  125.  
  126.     if (!form)
  127.         return;
  128.     for (i=0; i < form->nitems; i++)
  129.         item_delete(form, i);
  130.  
  131.     if (form->path)        free((void *)form->path);
  132.     if (form->name)        free((void *)form->name);
  133.     if (form->dbase)    free((void *)form->dbase);
  134.     if (form->comment)    free((void *)form->comment);
  135.     if (form->help)        free((void *)form->help);
  136.     if (form->query) {
  137.         for (i=0; i < form->nqueries; i++) {
  138.             dq = &form->query[i];
  139.             if (dq->name)    free((void *)dq->name);
  140.             if (dq->query)    free((void *)dq->query);
  141.         }
  142.         free((void *)form->query);
  143.     }
  144.     set_form_defaults(form);
  145. }
  146.  
  147.  
  148. /*
  149.  * print an error report and return FALSE if there are problems with the
  150.  * form. If there is an error, set *bug to the item# of the first buggy
  151.  * item; otherwise set to form->nitems. This is used to highlight the
  152.  * first incorrect item.
  153.  */
  154.  
  155. #define ISFLAG(i) (i->type == IT_FLAG || i->type == IT_CHOICE)
  156.  
  157. BOOL verify_form(
  158.     FORM        *form,        /* form to verify */
  159.     int        *bug,        /* retuirned buggy item # */
  160.     Widget        shell)        /* error popup parent */
  161. {
  162.     int        nitem, ni;    /* item counter */
  163.     ITEM        *item, *it;    /* item pointer */
  164.     int        nq;        /* query pointer */
  165.     DQUERY        *dq;        /* query pointer */
  166.     char        name[256];    /* current item's name */
  167.     char        msg[16384];    /* error messages */
  168.     int        i0, i = 0;    /* next free byte in msg[] */
  169.     int        sumwidth = 0;    /* total length of summary */
  170.  
  171.     if (bug)
  172.         *bug = form->nitems;
  173.     if (!form->name || !*form->name) {
  174.         sprintf(msg+i, "Form has no name\n");
  175.         i += strlen(msg+i);
  176.     }
  177.     if (!form->dbase || !*form->dbase) {
  178.         sprintf(msg+i, "Form has no database%s\n",
  179.                     form->name ? "using form name" : "");
  180.         i += strlen(msg+i);
  181.         form->dbase = mystrdup(form->name);
  182.     }
  183.     if (form->cdelim < 1) {
  184.         sprintf(msg+i, "Illegal field delimiter, using TAB\n");
  185.         i += strlen(msg+i);
  186.         form->cdelim = '\t';
  187.     }
  188.     for (nitem=0; nitem < form->nitems; nitem++) {
  189.         i0 = i;
  190.         item = form->items[nitem];
  191.         sumwidth += item->sumwidth;
  192.         sprintf(name, "Field \"%s\" (#%d)",
  193.                     item->name ? item->name : "", nitem);
  194.         if (!item->name || !*item->name) {
  195.             char newname[40];
  196.             sprintf(newname, "item%d", nitem);
  197.             sprintf(msg+i, "%s has no internal name, using %s\n",
  198.                                 name, newname);
  199.             i += strlen(msg+i);
  200.             item->name = mystrdup(newname);
  201.         }
  202.         if (item->xs <= 0 || item->ys <= 0) {
  203.             if (!item->xs) item->xs = 10;
  204.             if (!item->ys) item->ys = 10;
  205.             sprintf(msg+i, "%s has zero size, setting to %d %d\n",
  206.                         name, item->xs, item->ys);
  207.             i += strlen(msg+i);
  208.         }
  209.         if (!item->label && item->type != IT_VIEW) {
  210.             sprintf(msg+i, "%s has no label, using \"%s\"\n",
  211.                                 name, name);
  212.             i += strlen(msg+i);
  213.             item->label = mystrdup(name);
  214.         }
  215.         if (!item->flagcode && ISFLAG(item)) {
  216.             sprintf(msg+i, "%s has no flag code\n", name);
  217.             i += strlen(msg+i);
  218.         }
  219.         if (!item->pressed && item->type == IT_BUTTON) {
  220.             sprintf(msg+i, "%s has no button action\n", name);
  221.             i += strlen(msg+i);
  222.         }
  223.         if (!item->database && item->type == IT_VIEW) {
  224.             sprintf(msg+i, "%s has no query database\n", name);
  225.             i += strlen(msg+i);
  226.         }
  227.         if (!item->query && item->type == IT_VIEW) {
  228.             sprintf(msg+i, "%s has no query\n", name);
  229.             i += strlen(msg+i);
  230.         }
  231.         for (nq=0; nq < form->nqueries; nq++) {
  232.             dq = &form->query[nq];
  233.             if (!dq->suspended) {
  234.             if (!dq->name) {
  235.                 sprintf(msg+i, "Query %d has no name\n", nq+1);
  236.                 i += strlen(msg+i);
  237.             }
  238.             if (!dq->query) {
  239.                 sprintf(msg+i, "Query %d has no query\n",nq+1);
  240.                 i += strlen(msg+i);
  241.             }
  242.             }
  243.         }
  244.         for (ni=nitem+1; ni < form->nitems; ni++) {
  245.             it = form->items[ni];
  246.             if (item->type == IT_CHOICE && it->type == IT_CHOICE) {
  247.                 BOOL eq  = !strcmp(item->name, it->name);
  248.                 BOOL sam = FALSE;
  249.                 char *m  = 0;
  250.                 if      ((item->column   == it->column) != eq)
  251.                     m = "dbase column";
  252.                 else if ((item->sumcol   == it->sumcol) != eq)
  253.                     m = "summary column";
  254.                 else if ((item->sumwidth == it->sumwidth)!=eq)
  255.                     m = "summary width";
  256.                 else if ((item->flagcode == it->flagcode)&&eq){
  257.                     m = "flag code";
  258.                     sam = TRUE;
  259.                 }
  260.                 if (m && (eq || sam)) {
  261.                     sprintf(msg+i,
  262.                "(choice) has %s internal name as #%d, but has %s %s\n",
  263.                         eq  ? "same" : "different", ni,
  264.                         sam ? "same" : "different", m);
  265.                     i += strlen(msg+i);
  266.                 }
  267.                 continue;
  268.             }
  269.             if (item->name    && it->name
  270.                     && !strcmp(item->name, it->name)) {
  271.                 sprintf(msg+i,"%s has same name as field #%d\n"
  272.                                 ,name, ni);
  273.                 i += strlen(msg+i);
  274.             }
  275.             if (IN_DBASE(item->type) && IN_DBASE(it->type)
  276.                     && item->column == it->column
  277.                     && (!ISFLAG(item) || !ISFLAG(it))) {
  278.                 sprintf(msg+i,
  279.                "%s uses same dbase column as field \"%s\" (#%d)\n",
  280.                     name, it->name ? it->name : "", ni);
  281.                 i += strlen(msg+i);
  282.             }
  283.             if (IN_DBASE(item->type) && IN_DBASE(it->type)
  284.                     && item->sumwidth>0 && it->sumwidth>0
  285.                     && item->sumcol == it->sumcol) {
  286.                 sprintf(msg+i,
  287.              "%s uses same summary column as field \"%s\" (#%d)\n",
  288.                     name, it->name ? it->name : "", ni);
  289.                 i += strlen(msg+i);
  290.             }
  291.             if (i > sizeof(msg)-1024) {
  292.                 sprintf(msg+i, "Too many errors, aborted.");
  293.                 break;
  294.             }
  295.         }
  296.         if (i > sizeof(msg)-1024) {
  297.             sprintf(msg+i, "Too many errors, aborted.");
  298.             break;
  299.         }
  300.         if (i > i0 && bug && *bug == form->nitems)
  301.             *bug = nitem;
  302.     }
  303.     if (form->nitems && sumwidth == 0) {
  304.         sprintf(msg+i, "Summary is empty, all summary widths are 0\n");
  305.         i += strlen(msg+i);
  306.     }
  307.     if (i)
  308.         create_error_popup(shell, 0, msg);
  309.     else {
  310.         for (i=nitem=0; nitem < form->nitems; nitem++) {
  311.             item = form->items[nitem];
  312.             if (item->type == IT_NOTE && item->maxlen <= 100) {
  313.                 sprintf(msg+i,
  314.         "Warning: note field \"%s\" (#%d) has short max length %d",
  315.                     item->name ? item->name : "", nitem,
  316.                     item->maxlen);
  317.                 i += strlen(msg+i);
  318.             }
  319.         }
  320.         if (i)
  321.             create_error_popup(shell, 0, msg);
  322.         i = 0;
  323.     }
  324.     return(!i);
  325. }
  326.  
  327.  
  328. /*
  329.  * the database is procedural, and the edit button was pressed. Start up an
  330.  * editor window with fname, up to the first blank (this is useful for passing
  331.  * cmd line options later).
  332.  */
  333.  
  334. void form_edit_script(
  335.     FORM        *form,        /* form to edit */
  336.     Widget        shell,        /* error popup parent */
  337.     char        *fname)        /* file name of script (dbase name) */
  338. {
  339.     char        path[1024], *p, *q;
  340.  
  341.     if (!fname || !*fname) {
  342.         create_error_popup(shell, 0,
  343. "Please specify a database name first.\n\
  344. The database name will be used as script name.");
  345.         return;
  346.     }
  347.     form->proc = TRUE;
  348.     fillout_formedit_widget_by_code(0x105);
  349.  
  350.     for (p=fname, q=path; *p && *p != ' ' && *p != '\t'; p++, q++)
  351.         *q = *p;
  352.     *q = 0;
  353.     fname = resolve_tilde(path, 0);
  354.     edit_file(fname, FALSE, TRUE, "Procedural Database", "procdbedit");
  355. }
  356.  
  357.  
  358. /*
  359.  * sort the form's items by y, then x position (lower left corner). Later
  360.  * traversal in order can then be done simply by going down the list.
  361.  */
  362.  
  363. static int icompare(
  364.     MYCONST void    *u,
  365.     MYCONST void    *v)
  366. {
  367.     register ITEM    *iu = *(ITEM **)u;
  368.     register ITEM    *iv = *(ITEM **)v;
  369.  
  370.     return(iu->y+iu->ys == iv->y+iv->ys ? iu->x+iu->xs - iv->x-iv->xs
  371.                         : iu->y+iu->ys - iv->y-iv->ys);
  372. }
  373.  
  374.  
  375. void form_sort(
  376.     register FORM    *form)        /* form to sort */
  377. {
  378.     if (!form || !form->items || !form->nitems)
  379.         return;
  380.     qsort((void *)form->items, form->nitems, sizeof(ITEM *), icompare);
  381. }
  382.  
  383.  
  384. /*------------------------------------------------- operations on form items */
  385. /*
  386.  * deselect all items. The caller must make sure to set curr_item to the
  387.  * number of items in the current form (which means no selection). Here, only
  388.  * the selected flags are cleared. This happens when Unselect is pressed, but
  389.  * also if an item is added (we only want the added one selected afterwards).
  390.  */
  391.  
  392. void item_deselect(
  393.     register FORM    *form)        /* describes form and all items in it*/
  394. {
  395.     register ITEM    *item;        /* new item struct */
  396.     int        i;        /* index of item */
  397.  
  398.     for (i=0; i < form->nitems; i++) {
  399.         item = form->items[i];
  400.         if (item->selected) {
  401.             item->selected = FALSE;
  402.             redraw_canvas_item(item);
  403.         }
  404.     }
  405. }
  406.  
  407.  
  408. /*
  409.  * insert an item into the form, at the current field <nitem>. Fields are
  410.  * stored in the form, which contains an array of pointers that point to
  411.  * the field data structures. That way, we don't need to re-allocate the
  412.  * form struct itself, and items can be easily re-arranged.
  413.  * This assumes that fe_item_deselect has been called.
  414.  * Provide reasonable defaults for the new field.
  415.  */
  416.  
  417. BOOL item_create(
  418.     register FORM    *form,        /* describes form and all items in it*/
  419.     int        nitem)        /* the current item, insert point */
  420. {
  421.     register ITEM    *item;        /* new item struct */
  422.     register ITEM    *prev;        /* prev item, plundered for defaults */
  423.     register int    i, j, n, t;    /* various counters */
  424.     char        buf[80];    /* temp for default strings */
  425.  
  426.                             /* allocate array */
  427.     if (!form->items || form->nitems >= form->size) {
  428.         i = (form->size + CHUNK) * sizeof(ITEM *);
  429.         if (!(form->items = (ITEM **)(form->items
  430.                     ? realloc((void *)form->items, i)
  431.                     : malloc(i)))) {
  432.             create_error_popup(toplevel, errno,
  433.                     "Can't create field");
  434.             return(FALSE);
  435.         }
  436.         form->size += CHUNK;
  437.     }
  438.                             /* allocate item */
  439.     if (!(item = (ITEM *)malloc(sizeof(ITEM)))) {
  440.         create_error_popup(toplevel, errno, "Can't create field");
  441.         return(FALSE);
  442.     }
  443.     for (i=form->nitems-1; i >= nitem; i--)
  444.         form->items[i+1] = form->items[i];
  445.     form->items[nitem] = item;
  446.     form->nitems++;
  447.                             /* defaults */
  448.     if (nitem < form->nitems-1) {
  449.         (void)memcpy(item, form->items[nitem+1], sizeof(ITEM));
  450.         item->name       = mystrdup(item->name);
  451.         item->flagcode       = mystrdup(item->flagcode);
  452.         item->label       = mystrdup(item->label);
  453.         item->gray_if       = mystrdup(item->gray_if);
  454.         item->freeze_if       = mystrdup(item->freeze_if);
  455.         item->invisible_if = mystrdup(item->invisible_if);
  456.         item->skip_if       = mystrdup(item->skip_if);
  457.         item->idefault       = mystrdup(item->idefault);
  458.         item->pattern       = mystrdup(item->pattern);
  459.         item->pressed       = mystrdup(item->pressed);
  460.         item->added       = mystrdup(item->added);
  461.         item->database       = mystrdup(item->database);
  462.         item->query       = mystrdup(item->query);
  463.     } else {
  464.         mybzero((void *)item, sizeof(ITEM));
  465.         item->type       = IT_INPUT;
  466.         item->selected       = TRUE;
  467.         item->labeljust       = J_LEFT;
  468.         item->inputjust       = J_LEFT;
  469.         item->column       = 1;
  470.         item->minlen       = 1;
  471.         item->maxlen       = 100;
  472.         item->ch_xmax       = 1;
  473.         item->ch_ymax       = 1;
  474.         item->qsummary       = TRUE;
  475.         item->qlast       = TRUE;
  476.         item->x        = XSNAP(8) + form->xg;
  477.         item->xs       = XSNAP(form->xs - 32);
  478.         item->xm       = XSNAP(item->xs / 4);
  479.         item->y        = YSNAP(8) + form->xg;
  480.         item->ys       = YSNAP(24) + form->yg;
  481.         item->ym       = item->ys;
  482.         if (nitem) {
  483.             prev = form->items[nitem-1];
  484.             for (i=0; i < form->nitems; i++)
  485.                 if (form->items[i]->type == item->type) {
  486.                     prev = form->items[i];
  487.                     item->xs = prev->xs;
  488.                     item->ys = prev->xs;
  489.                     item->xm = prev->ym;
  490.                     item->ym = prev->ym;
  491.                     break;
  492.                 }
  493.             item->labelfont = prev->labelfont;
  494.             item->inputfont = prev->inputfont;
  495.         }
  496.     }
  497.                             /* move to bottom */
  498.     for (i=0; i < form->nitems; i++) {
  499.         j = form->items[i]->y + form->items[i]->ys;
  500.         j = YSNAP(j + 8);
  501.         if (i != nitem && j > item->y)
  502.             item->y = j;
  503.     }
  504.                             /* find unused column*/
  505.     item->column = 0;
  506.     if (item->type == IT_CHOICE || item->type == IT_FLAG)
  507.         item->flagcode = 0;
  508.     else {
  509.         if (IN_DBASE(item->type)) {
  510.             n = form->nitems;
  511.             for (i=0; i < n; i++)
  512.                 for (j=0; j < n; j++) {
  513.                     t = form->items[j]->type;
  514.                     if (IN_DBASE(t) &&
  515.                         form->items[j] != item &&
  516.                         form->items[j]->column ==
  517.                                 item->column) {
  518.                         item->column++;
  519.                         break;
  520.                     }
  521.                 }
  522.         }
  523.         sprintf(buf, "item%d", item->column);
  524.         item->name = mystrdup(buf);
  525.     }
  526.     return(TRUE);
  527. }
  528.  
  529.  
  530. /*
  531.  * delete a field from the form definition.
  532.  */
  533.  
  534. void item_delete(
  535.     FORM        *form,        /* describes form and all items in it*/
  536.     int        nitem)        /* the current item, insert point */
  537. {
  538.     register ITEM    *item = form->items[nitem];
  539.     int        i;
  540.  
  541.     if (item->name)        free((void *)item->name);
  542.     if (item->label)    free((void *)item->label);
  543.     if (item->flagcode)    free((void *)item->flagcode);
  544.     if (item->gray_if)    free((void *)item->gray_if);
  545.     if (item->freeze_if)    free((void *)item->freeze_if);
  546.     if (item->invisible_if)    free((void *)item->invisible_if);
  547.     if (item->skip_if)    free((void *)item->skip_if);
  548.     if (item->idefault)    free((void *)item->idefault);
  549.     if (item->pattern)    free((void *)item->pattern);
  550.     if (item->pressed)    free((void *)item->pressed);
  551.     if (item->added)    free((void *)item->added);
  552.     if (item->ch_xexpr)    free((void *)item->ch_xexpr);
  553.     if (item->ch_yexpr)    free((void *)item->ch_yexpr);
  554.     if (item->ch_bar)    free((void *)item->ch_bar);
  555.     if (item->database)    free((void *)item->database);
  556.     if (item->query)    free((void *)item->query);
  557.  
  558.     for (i=0; i < item->ch_ncomp; i++) {
  559.         item->ch_curr = i;
  560.         del_chart_component(item);
  561.     }
  562.     if (item->ch_comp)
  563.         free((void *)item->ch_comp);
  564.     free((void *)item);
  565.  
  566.     for (i=nitem; i < form->nitems-1; i++)
  567.         form->items[i] = form->items[i+1];
  568.     form->nitems--;
  569. }
  570.  
  571.  
  572. /*
  573.  * clone a field from the form definition. This is used when the form is
  574.  * cloned when the form editor starts up with the form that is also being
  575.  * displayed (never operate on the displayed form directly).
  576.  */
  577.  
  578. ITEM *item_clone(
  579.     register ITEM    *parent)    /* item to clone */
  580. {
  581.     register ITEM    *item;        /* target item */
  582.     int        i;
  583.  
  584.     if (!(item = (ITEM *)malloc(sizeof(ITEM)))) {
  585.         create_error_popup(toplevel, errno, "Can't clone field");
  586.         return(0);
  587.     }
  588.     *item = *parent;
  589.     item->name       = mystrdup(parent->name);
  590.     item->label       = mystrdup(parent->label);
  591.     item->flagcode       = mystrdup(parent->flagcode);
  592.     item->gray_if       = mystrdup(parent->gray_if);
  593.     item->freeze_if       = mystrdup(parent->freeze_if);
  594.     item->invisible_if = mystrdup(parent->invisible_if);
  595.     item->skip_if       = mystrdup(parent->skip_if);
  596.     item->idefault       = mystrdup(parent->idefault);
  597.     item->pattern       = mystrdup(parent->pattern);
  598.     item->pressed       = mystrdup(parent->pressed);
  599.     item->added       = mystrdup(parent->added);
  600.     item->ch_xexpr       = mystrdup(parent->ch_xexpr);
  601.     item->ch_yexpr       = mystrdup(parent->ch_yexpr);
  602.     item->database       = mystrdup(parent->database);
  603.     item->query       = mystrdup(parent->query);
  604.     item->ch_bar       = 0;
  605.     item->ch_nbars       = 0;
  606.  
  607.     if (item->ch_comp) {
  608.         item->ch_comp = (CHART*)malloc(item->ch_ncomp * sizeof(CHART));
  609.         for (i=0; i < item->ch_ncomp; i++)
  610.             clone_chart_component(&item->ch_comp[i],
  611.                         &parent->ch_comp[i]);
  612.     }
  613.     return(item);
  614. }
  615.